﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Framework.UnitOfWork;
using Framework.Infrastructure.Context;
using Norm.Collections;
using Framework.Storage;
using System.Linq.Expressions;
using Framework.Specifications;
using Framework.Infrastructure;
namespace Framework.Repositories
{
    public class MongoDBCtxRepository<TEntity> : Repository<TEntity>
         where TEntity : class, IAggregateRoot, new()
    {
        public MongoDBCtxRepository(IUnitOfWork unitOfWork)
            : base(unitOfWork)
        {
        }

        public MongoDBContext Container
        {
            get { return UnitOfWork.DbContext as MongoDBContext; }
        }

        IMongoCollection<TEntity> ObjectSet
        {
            get { return _objectSet ?? (_objectSet = Container.GetCollection<TEntity>()); }
        }
        IMongoCollection<TEntity> _objectSet;


        protected override void DoAdd(IQueryable<TEntity> entities)
        {
            Container.Add<TEntity>(entities);
        }

        protected override void DoAdd(TEntity entity)
        {
            Container.Add<TEntity>(entity);
        }

        protected override TEntity DoGetByKey(params object[] keyValues)
        {
            return ObjectSet.FindOne(new { _id = keyValues[0] });
        }

        protected override IQueryable<TEntity> DoGetAll(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> sortPredicate, SortOrder sortOrder, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            var query = ObjectSet.AsQueryable();
            if (specification != null)
                query = query.Where(specification.GetExpression());

            if (sortPredicate == null)
            {
                return query;
            }
            if (sortOrder.Equals(SortOrder.Descending))
            {
                return query.OrderByDescending(sortPredicate);
            }
            else
            {
                return query.OrderBy(sortPredicate);
            }
        }

        protected override IQueryable<TEntity> DoFindAll(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> sortPredicate, SortOrder sortOrder, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            return DoGetAll(specification, sortPredicate, sortOrder);
        }

        protected override TEntity DoGet(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            return ObjectSet.AsQueryable().FirstOrDefault(specification.GetExpression());
        }

        protected override TEntity DoFind(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> path = null)
        {
            return DoGet(specification);
        }

        protected override bool DoExists(ISpecification<TEntity> specification)
        {
            return ObjectSet.Count(specification.GetExpression()) > 0;
        }

        protected override void DoRemove(TEntity entity)
        {
            Container.Delete(entity);
        }

        protected override void DoUpdate(TEntity entity)
        {
            Container.Update<TEntity>(entity);
        }

        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, Expression<Func<TEntity, dynamic>> orderByExpression, ISpecification<TEntity> specification, bool ascending, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            if (orderByExpression == (Expression<Func<TEntity, dynamic>>)null)
                throw new ArgumentNullException("OrderByExpressionCannotBeNull");

            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }

            //Create associated IObjectSet and perform query

            //this query cannot  use Paginate IQueryable extension method because Linq queries cannot be
            //merged with Object Builder methods. See Entity Framework documentation for more information
            IQueryable<TEntity> query = ObjectSet.AsQueryable().Where(specification.GetExpression());
        
            return (ascending)
                                ?
                                    query
                                     .OrderBy(orderByExpression)
                                     .GetPageElements(pageIndex, pageSize)
                                :
                                    query
                                     .OrderByDescending(orderByExpression)
                                     .GetPageElements(pageIndex, pageSize);
        }

        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, Expression<Func<TEntity, dynamic>> orderByExpression, ISpecification<TEntity> specification, bool ascending, ref long totalCount, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            if (orderByExpression == (Expression<Func<TEntity, dynamic>>)null)
                throw new ArgumentNullException("OrderByExpressionCannotBeNull");

            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }

            //Create associated IObjectSet and perform query

            IQueryable<TEntity> query = ObjectSet.AsQueryable().Where(specification.GetExpression());
            totalCount = ObjectSet.AsQueryable().Count(specification.GetExpression());


         
            return (ascending)
                            ?
                                query
                                .OrderBy(orderByExpression)
                                .GetPageElements(pageIndex, pageSize)
                            :
                                query
                                .OrderByDescending(orderByExpression)
                                .GetPageElements(pageIndex, pageSize);
        }

        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, string[] orderByFileds, ISpecification<TEntity> specification, bool ascending, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            var orderBys = new List<LambdaExpression>();
            if (orderByFileds != null && orderByFileds.Length > 0)
            {
                foreach (var field in orderByFileds)
                {
                    var le = Utility.GetLambdaExpression(typeof(TEntity), field);
                    orderBys.Add(le);
                }
            }
            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }
            //Create associated IObjectSet and perform query

            IQueryable<TEntity> query = ObjectSet.AsQueryable().Where(specification.GetExpression());

            for (int i = 0; i < orderBys.Count; i++)
            {
                var orderByExpression = orderBys[i];
                string orderByCMD = "OrderBy";
                if (i > 0)
                {
                    if (!ascending)
                    {
                        orderByCMD = "ThenByDescending";
                    }
                    else
                    {
                        orderByCMD = "ThenBy";
                    }
                }
                else
                {
                    if (!ascending)
                    {
                        orderByCMD = "OrderByDescending";
                    }
                }

                MethodCallExpression orderByCallExpression =
                        Expression.Call(typeof(Queryable),
                        orderByCMD,
                        new Type[] { typeof(TEntity),
                        orderByExpression.Body.Type},
                        query.Expression,
                        orderByExpression);
                query = query.Provider.CreateQuery<TEntity>(orderByCallExpression);

            }
            return query.GetPageElements(pageIndex, pageSize);
        }


        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, string[] orderByFileds, ISpecification<TEntity> specification, bool ascending, ref long totalCount, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            var orderBys = new List<LambdaExpression>();
            if (orderByFileds != null && orderByFileds.Length > 0)
            {
                foreach (var field in orderByFileds)
                {
                    var le = Utility.GetLambdaExpression(typeof(TEntity), field);
                    orderBys.Add(le);
                }
            }

            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }

            //Create associated IObjectSet and perform query

            //this query cannot  use Paginate IQueryable extension method because Linq queries cannot be
            //merged with Object Builder methods. See Entity Framework documentation for more information
            IQueryable<TEntity> query = ObjectSet.AsQueryable().Where(specification.GetExpression());
            totalCount = ObjectSet.AsQueryable().Count(specification.GetExpression());

            for (int i = 0; i < orderBys.Count; i++)
            {
                var orderByExpression = orderBys[i];
                string orderByCMD = "OrderBy";
                if (i > 0)
                {
                    if (!ascending)
                    {
                        orderByCMD = "ThenByDescending";
                    }
                    else
                    {
                        orderByCMD = "ThenBy";
                    }
                }
                else
                {
                    if (!ascending)
                    {
                        orderByCMD = "OrderByDescending";
                    }
                }
                MethodCallExpression orderByCallExpression =
                        Expression.Call(typeof(Queryable),
                        orderByCMD,
                        new Type[] { typeof(TEntity),
                        orderByExpression.Body.Type},
                        query.Expression,
                        orderByExpression);
                query = query.Provider.CreateQuery<TEntity>(orderByCallExpression);
            }
            return query.GetPageElements(pageIndex, pageSize);
        }
    }
}